
// these are all the viewer dialogs except for the ones with graphical areas
// they can be instantiated multiple times

#include "wxvbam.h"
#include "viewsupt.h"
#include "../gba/armdis.h"
#include <wx/vlbox.h>
#include <wx/ffile.h>

// avoid exporting classes
namespace Viewers
{
#define gethex(v, n, l) do { \
    v = XRCCTRL(*this, n, wxTextCtrl); \
    if(!v) \
    baddialog(); \
    wxTextValidator hv(wxFILTER_INCLUDE_CHAR_LIST); \
    hv.SetIncludes(val_hexdigits); \
    v->SetValidator(hv); \
    v->SetMaxLength(l); \
} while(0)

class DisassembleViewer : public Viewer
{
public:
	DisassembleViewer() : Viewer(wxT("Disassemble"))
	{
		gethex(goto_addr, "GotoAddress", 8);
		goto_addr->SetFocus();

		for (int i = 0; i < 17; i++)
		{
			// it is unfortunately impossible to reliably assign
			// number ranges to XRC IDs, so the string name has to be
			// used every time
			wxString n;
			n.Printf(wxT("R%d"), i);
			regv[i] = XRCCTRL_D(*this, n, wxControl);

			if (!regv[i])
				baddialog();
		}

#define flagctrl(n) do { \
    n = XRCCTRL(*this, #n, wxCheckBox); \
    if(!n) \
    baddialog(); \
} while(0)
#define regctrl(n) do { \
    n##v = XRCCTRL(*this, #n, wxControl); \
    if(!n##v) \
    baddialog(); \
} while(0)
		flagctrl(N);
		flagctrl(Z);
		flagctrl(C);
		flagctrl(V);
		flagctrl(I);
		flagctrl(F);
		flagctrl(T);
		regctrl(Mode);
		dis = XRCCTRL(*this, "Disassembly", DisList);

		if (!dis)
			baddialog();

		// refit listing for longest line
		dis->Refit(70);
		Fit();
		SetMinSize(GetSize());
		dis->maxaddr = (u32)~0;
		dismode = 0;
		GotoPC();
	}
	void Update()
	{
		GotoPC();
	}
	void Next(wxCommandEvent &ev)
	{
		CPULoop(1);
		GotoPC();
	}
	void Goto(wxCommandEvent &ev)
	{
		wxString as = goto_addr->GetValue();

		if (!as.size())
			return;

		long a;
		as.ToLong(&a, 16);
		dis->SetSel(a);
		UpdateDis();
	}
	// wx-2.8.4 or MacOSX compiler can't resolve overloads in evt table
	void GotoPCEv(wxCommandEvent &ev)
	{
		GotoPC();
	}
	void GotoPC()
	{
#if 0

		// this is what the win32 interface used
		if (armState)
			dis->SetSel(armNextPC - 16);
		else
			dis->SetSel(armNextPC - 8);

		// doesn't make sense, though.  Maybe it's just trying to keep the
		// sel 4 instructions below top...
#endif
		dis->SetSel(armNextPC);
		UpdateDis();
	}
	void RefreshCmd(wxCommandEvent &ev)
	{
		UpdateDis();
	}
	void UpdateDis()
	{
		N->SetValue(reg[16].I & 0x80000000);
		Z->SetValue(reg[16].I & 0x40000000);
		C->SetValue(reg[16].I & 0x20000000);
		V->SetValue(reg[16].I & 0x10000000);
		I->SetValue(reg[16].I & 0x00000080);
		F->SetValue(reg[16].I & 0x00000040);
		T->SetValue(reg[16].I & 0x00000020);
		wxString s;
		s.Printf(wxT("%02X"), reg[16].I & 0x1f);
		Modev->SetLabel(s);

		for (int i = 0; i < 17; i++)
		{
			s.Printf(wxT("%08X"), reg[i].I);
			regv[i]->SetLabel(s);
		}
	}

	void RefillListEv(wxCommandEvent &ev)
	{
		// what an unsafe calling convention
		// examination of disArm shows that max len is 69 chars
		// (e.g. 0x081cb6db), and I assume disThumb is shorter
		char buf[80];
		dis->strings.clear();
		dis->addrs.clear();
		u32 addr = dis->topaddr;
		bool arm = dismode == 1 || (armState && dismode != 2);
		dis->back_size = arm ? 4 : 2;

		for (int i = 0; i < dis->nlines; i++)
		{
			dis->addrs.push_back(addr);

			if (arm)
				addr += disArm(addr, buf, DIS_VIEW_CODE | DIS_VIEW_ADDRESS);
			else
				addr += disThumb(addr, buf, DIS_VIEW_CODE | DIS_VIEW_ADDRESS);

			dis->strings.push_back(wxString(buf, wxConvLibc));
		}

		dis->Refill();
	}

	DisList* dis;
	wxTextCtrl* goto_addr;
	wxCheckBox* N, *Z, *C, *V, *I, *F, *T;
	int dismode;
	wxControl* regv[17], *Modev;
	DECLARE_EVENT_TABLE()
};

BEGIN_EVENT_TABLE(DisassembleViewer, Viewer)
	EVT_COMMAND(wxID_ANY, EVT_REFILL_NEEDED, DisassembleViewer::RefillListEv)
	EVT_BUTTON(XRCID("Goto"), DisassembleViewer::Goto)
	EVT_TEXT_ENTER(XRCID("GotoAddress"), DisassembleViewer::Goto)
	EVT_BUTTON(XRCID("GotoPC"), DisassembleViewer::GotoPCEv)
	EVT_BUTTON(XRCID("Next"), DisassembleViewer::Next)
	EVT_BUTTON(XRCID("Refresh"), DisassembleViewer::RefreshCmd)
END_EVENT_TABLE()

class GBDisassembleViewer : public Viewer
{
public:
	GBDisassembleViewer() : Viewer(wxT("GBDisassemble"))
	{
		gethex(goto_addr, "GotoAddress", 4);
		goto_addr->SetFocus();
		regctrl(AF);
		regctrl(BC);
		regctrl(DE);
		regctrl(HL);
		regctrl(SP);
		regctrl(PC);
		regctrl(LY);
		regctrl(IFF);
		flagctrl(Z);
		flagctrl(N);
		flagctrl(H);
		flagctrl(C);
		dis = XRCCTRL(*this, "Disassembly", DisList);

		if (!dis)
			baddialog();

		// refit listing for longest line
		dis->Refit(26);
		Fit();
		SetMinSize(GetSize());
		dis->maxaddr = (u32)~0;
		GotoPC();
	}
	void Update()
	{
		GotoPC();
	}
	void Next(wxCommandEvent &ev)
	{
		gbEmulate(1);
		GotoPC();
	}
	void Goto(wxCommandEvent &ev)
	{
		wxString as = goto_addr->GetValue();

		if (!as.size())
			return;

		long a;
		as.ToLong(&a, 16);
		dis->SetSel(a);
		UpdateDis();
	}
	// wx-2.8.4 or MacOSX compiler can't resolve overloads in evt table
	void GotoPCEv(wxCommandEvent &ev)
	{
		GotoPC();
	}
	void GotoPC()
	{
		dis->SetSel(PC.W);
		UpdateDis();
	}
	void RefreshCmd(wxCommandEvent &ev)
	{
		UpdateDis();
	}
	void UpdateDis()
	{
		Z->SetValue(AF.B.B0 & Z_FLAG);
		N->SetValue(AF.B.B0 & N_FLAG);
		H->SetValue(AF.B.B0 & H_FLAG);
		C->SetValue(AF.B.B0 & C_FLAG);
#define grv16(n, val) do { \
    wxString s; \
    s.Printf(wxT("%04X"), (int)val); \
    n##v->SetLabel(s); \
} while(0)
#define rv16(n) grv16(n, n.W)
#define srv16(n) grv16(n, n)
		rv16(AF);
		rv16(BC);
		rv16(DE);
		rv16(HL);
		rv16(SP);
		rv16(PC);
		srv16(IFF);
		wxString s;
		s.Printf(wxT("%02X"), register_LY);
		LYv->SetLabel(s);
	}

	void RefillListEv(wxCommandEvent &ev)
	{
		// what an unsafe calling convention
		// examination of gbDis shows that max len is 26 chars
		// (e.g. 0xe2)
		char buf[30];
		u16 addr = dis->topaddr;
		dis->strings.clear();
		dis->addrs.clear();
		dis->back_size = 1;

		for (int i = 0; i < dis->nlines; i++)
		{
			dis->addrs.push_back(addr);
			addr += gbDis(buf, addr);
			dis->strings.push_back(wxString(buf, wxConvLibc));
		}

		dis->Refill();
	}

	DisList* dis;
	wxTextCtrl* goto_addr;
	wxControl* AFv, *BCv, *DEv, *HLv, *SPv, *PCv, *LYv, *IFFv;
	wxCheckBox* Z, *N, *H, *C;
	DECLARE_EVENT_TABLE()
};
BEGIN_EVENT_TABLE(GBDisassembleViewer, Viewer)
	EVT_COMMAND(wxID_ANY, EVT_REFILL_NEEDED, GBDisassembleViewer::RefillListEv)
	EVT_BUTTON(XRCID("Goto"), GBDisassembleViewer::Goto)
	EVT_TEXT_ENTER(XRCID("GotoAddress"), GBDisassembleViewer::Goto)
	EVT_BUTTON(XRCID("GotoPC"), GBDisassembleViewer::GotoPCEv)
	EVT_BUTTON(XRCID("Next"), GBDisassembleViewer::Next)
	EVT_BUTTON(XRCID("Refresh"), GBDisassembleViewer::RefreshCmd)
END_EVENT_TABLE()
}

void MainFrame::Disassemble(void)
{
	switch (panel->game_type())
	{
	case IMAGE_GBA:
		LoadXRCViewer(Disassemble);
		break;

	case IMAGE_GB:
		LoadXRCViewer(GBDisassemble);
		break;
	}
}

// for CPUWriteHalfWord
// and CPURead... below
#include "../gba/GBAinline.h"

namespace Viewers
{
#include "ioregs.h"
class IOViewer : public Viewer
{
public:
	IOViewer() : Viewer(wxT("IOViewer"))
	{
		for (int i = 0; i < 16; i++)
		{
			wxString s;
			s.Printf(wxT("B%d"), i);
			bit[i] = XRCCTRL_D(*this, s, wxCheckBox);
			s.append(wxT("lab"));
			bitlab[i] = XRCCTRL_D(*this, s, wxControl);

			if (!bit[i] || !bitlab[i])
				baddialog();
		}

		addr = XRCCTRL(*this, "Address", wxChoice);
		val = XRCCTRL(*this, "Value", wxControl);

		if (!addr || !val)
			baddialog();

		addr->Clear();
		const wxChar* longline = lline;
		int lwidth = 0;

		for (int i = 0; i < NUM_IOREGS; i++)
		{
			addr->Append(wxGetTranslation(ioregs[i].name));

			// find longest label
			// this is probably horribly expensive
			// cache for entire app...
			// and while at it, translate all the strings
			if (!lline)
			{
				for (int j = 0; j < 16; j++)
				{
					if (ioregs[i].bits[j][0])
					{
						ioregs[i].bits[j] = wxGetTranslation(ioregs[i].bits[j]);
						int w, h;
						bitlab[0]->GetTextExtent(ioregs[i].bits[j], &w, &h);

						if (w > lwidth)
						{
							lwidth = w;
							longline = ioregs[i].bits[j];
						}
					}
				}
			}
		}

		if (!lline)
			lline = longline;

		bitlab[0]->SetLabel(lline);
		Fit();
		addr->SetSelection(0);
		Select(0);
	}

	void SelectEv(wxCommandEvent &ev)
	{
		Select(addr->GetSelection());
	}

	void Select(int sel)
	{
		int i;
		u16 mask;

		for (mask = 1, i = 0; mask; mask <<= 1, i++)
		{
			bit[i]->Enable(mask & ioregs[sel].write);
			bitlab[i]->SetLabel(ioregs[sel].bits[i]);
		}

		Update(sel);
	}

	void Update()
	{
		Update(addr->GetSelection());
	}

	void Update(int sel)
	{
		u16* addr = ioregs[sel].address ? ioregs[sel].address :
		            (u16*)&ioMem[ioregs[sel].offset];
		u16 mask, reg = *addr;
		int i;

		for (mask = 1, i = 0; mask; mask <<= 1, i++)
			bit[i]->SetValue(mask & reg);

		wxString s;
		s.Printf(wxT("%04X"), reg);
		val->SetLabel(s);
	}

	void CheckBit(wxCommandEvent &ev)
	{
		for (int i = 0; i < 16; i++)
			if (ev.GetEventObject() == bit[i])
			{
				// it'd be faster to store the value and just flip
				// the bit, but it's easier this way
				u16 mask, reg = 0;
				int j;

				for (mask = 1, j = 0; mask; mask <<= 1, j++)
					if (bit[j]->GetValue())
						reg |= mask;

				wxString s;
				s.Printf(wxT("%04X"), reg);
				val->SetLabel(s);
				return;
			}

		ev.Skip();
	}

	void RefreshEv(wxCommandEvent &ev)
	{
		Update();
	}

	void Apply(wxCommandEvent &ev)
	{
		int sel = addr->GetSelection();
		u16* addr = ioregs[sel].address ? ioregs[sel].address :
		            (u16*)&ioMem[ioregs[sel].offset];
		u16 mask, reg = *addr;
		reg &= ~ioregs[sel].write;
		int i;

		for (mask = 1, i = 0; mask; mask <<= 1, i++)
		{
			if ((mask & ioregs[sel].write) && bit[i]->GetValue())
				reg |= mask;
		}

		CPUWriteHalfWord(0x4000000 + ioregs[sel].offset, reg);
		Update(sel);
	}

	static const wxChar* lline;
	wxChoice* addr;
	wxControl* val;
	wxCheckBox* bit[16];
	wxControl* bitlab[16];
	DECLARE_EVENT_TABLE()
};
const wxChar* IOViewer::lline = NULL;
BEGIN_EVENT_TABLE(IOViewer, Viewer)
	EVT_BUTTON(XRCID("Refresh"), IOViewer::RefreshEv)
	EVT_BUTTON(wxID_APPLY, IOViewer::Apply)
	EVT_CHOICE(XRCID("Address"), IOViewer::SelectEv)
	EVT_CHECKBOX(wxID_ANY, IOViewer::CheckBit)
END_EVENT_TABLE()
}

void MainFrame::IOViewer()
{
	LoadXRCViewer(IO);
}

#define getlogf(n, val) do { \
    wxCheckBox *cb = XRCCTRL(*this, n, wxCheckBox); \
    if(!cb) \
    baddialog(); \
    cb->SetValidator(wxBoolIntValidator(&systemVerbose, val, val)); \
} while(0)
LogDialog::LogDialog()
{
	const wxChar* dname = wxT("Logging");

	if (!wxXmlResource::Get()->LoadDialog(this, wxGetApp().frame, dname))
		baddialog();

	SetEscapeId(wxID_OK);
	getlogf("SWI", VERBOSE_SWI);
	getlogf("UnalignedMemory", VERBOSE_UNALIGNED_MEMORY);
	getlogf("IllWrite", VERBOSE_ILLEGAL_WRITE);
	getlogf("IllRead", VERBOSE_ILLEGAL_READ);
	getlogf("DMA0", VERBOSE_DMA0);
	getlogf("DMA1", VERBOSE_DMA1);
	getlogf("DMA2", VERBOSE_DMA2);
	getlogf("DMA3", VERBOSE_DMA3);
	getlogf("UndefInstruction", VERBOSE_UNDEFINED);
	getlogf("AGBPrint", VERBOSE_AGBPRINT);
	getlogf("SoundOut", VERBOSE_SOUNDOUTPUT);
	log = XRCCTRL(*this, "Log", wxTextCtrl);

	if (!log)
		baddialog();

	Fit();
}

void LogDialog::Update()
{
	wxString l = wxGetApp().log;
	log->SetValue(l);
	log->ShowPosition(l.size() > 2 ? l.size() - 2 : 0);
}

void LogDialog::Save(wxCommandEvent &ev)
{
	static wxString logdir = wxEmptyString, def_name = wxEmptyString;

	if (def_name.empty())
		def_name = wxGetApp().frame->GetPanel()->game_name() + wxT(".log");

	wxString pats = _("Text files (*.txt;*.log)|*.txt;*.log|");
	pats.append(wxALL_FILES);
	wxFileDialog dlg(this, _("Select output file"), logdir, def_name,
	                 pats, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
	int ret = dlg.ShowModal();
	def_name = dlg.GetPath();
	logdir = dlg.GetDirectory();

	if (ret != wxID_OK)
		return;

	wxFFile f(def_name, wxT("w"));

	if (f.IsOpened())
	{
		f.Write(wxGetApp().log);
		f.Close();
	}
}

void LogDialog::Clear(wxCommandEvent &ev)
{
	wxGetApp().log.clear();
	Update();
}

BEGIN_EVENT_TABLE(LogDialog, wxDialog)
	EVT_BUTTON(wxID_SAVE, LogDialog::Save)
	EVT_BUTTON(XRCID("Clear"), LogDialog::Clear)
	EVT_CHECKBOX(wxID_ANY, Viewers::Viewer::ActiveCtrl)
END_EVENT_TABLE()

// These are what mfc interface used.  Maybe it would be safer
// to avoid the Quick() routines in favor of the long ones...
#define CPUWriteByteQuick(addr, b) \
  ::map[(addr)>>24].address[(addr) & ::map[(addr)>>24].mask] = (b)
#define CPUWriteHalfWordQuick(addr, b) \
  WRITE16LE((u16 *)&::map[(addr)>>24].address[(addr) & ::map[(addr)>>24].mask], b)
#define CPUWriteMemoryQuick(addr, b) \
  WRITE32LE((u32 *)&::map[(addr)>>24].address[(addr) & ::map[(addr)>>24].mask], b)
#define GBWriteByteQuick(addr, b) \
  *((u8 *)&gbMemoryMap[(addr)>>12][(addr) & 0xfff]) = (b)
#define GBWriteHalfWordQuick(addr, b) \
  WRITE16LE((u16 *)&gbMemoryMap[(addr)>>12][(addr) & 0xfff], b)
#define GBWriteMemoryQuick(addr, b) \
  WRITE32LE((u32 *)&gbMemoryMap[(addr)>>12][(addr) & 0xfff], b)
#define GBReadMemoryQuick(addr) \
  READ32LE((u32 *)&gbMemoryMap[(addr)>>12][(addr) & 0xfff])

namespace Viewers
{
static wxString memsave_dir = wxEmptyString;
class MemViewerBase : public Viewer
{
public:
	MemViewerBase(u32 max) : Viewer(wxT("MemViewer"))
	{
		if (!(mv = XRCCTRL(*this, "MemView", MemView)))
			baddialog();

		if (!(bs = XRCCTRL(*this, "BlockStart", wxChoice)))
			baddialog();

		bs->Append(wxT(""));
		bs->SetFocus();
		mv->fmt = max > 0xffff ? 2 : 1;
		getradio(, "Fmt8", mv->fmt, 0);
		getradio(, "Fmt16", mv->fmt, 1);
		getradio(, "Fmt32", mv->fmt, 2);
		mv->maxaddr = max;
		addrlen = max > 0xffff ? 8 : 4;
		gethex(goto_addr, "GotoAddress", addrlen);
		wxControl* addr = XRCCTRL(*this, "CurAddress", wxControl);

		if (!addr)
			baddialog();

		addr->SetLabel(addrlen == 8 ? wxT("0xWWWWWWWW") : wxT("0xWWWW"));
		// refit listing for longest line
		mv->Refit();
		Fit();
		// don't let address display resize when window size changes
		addr->SetMinSize(addr->GetSize());
		mv->addrlab = addr;
		SetMinSize(GetSize());
		Goto(0);
		// initialize load/save support dialog already
		{
			const wxChar* dname = wxT("MemSelRegion");
			selregion = wxXmlResource::Get()->LoadDialog(this, dname);

			if (!selregion)
				baddialog();

#define this selregion // for gethex()
			gethex(selreg_addr, "Address", addrlen);
			gethex(selreg_len, "Size", addrlen);
#undef this
			selreg_lenlab = XRCCTRL(*selregion, "SizeLab", wxControl);
			selregion->Fit();
		}
	}
	void BlockStart(wxCommandEvent &ev)
	{
		unsigned long l;
		bs->GetStringSelection().ToULong(&l, 0);
		Goto(l);
	}
	void GotoEv(wxCommandEvent &ev)
	{
		unsigned long l;
		wxString v = goto_addr->GetValue();

		if (v.empty())
			return;

		v.ToULong(&l, 16);
		Goto(l);
	}
	void Goto(u32 addr)
	{
		mv->Show(addr, true);
	}
	void RefreshCmd(wxCommandEvent &ev)
	{
		Update();
	}

	virtual void Update() {}

	void Load(wxCommandEvent &ev)
	{
		if (memsave_fn.empty())
			memsave_fn = wxGetApp().frame->GetPanel()->game_name() + wxT(".dmp");

		wxString pats = _("Memory dumps (*.dmp;*.bin)|*.dmp;*.bin|");
		pats.append(wxALL_FILES);
		wxFileDialog dlg(this, _("Select memory dump file"), memsave_dir, memsave_fn,
		                 pats, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
		int ret = dlg.ShowModal();
		memsave_fn = dlg.GetPath();
		memsave_dir = dlg.GetDirectory();

		if (ret != wxID_OK)
			return;

		wxFileName fn(memsave_fn);

		if (!fn.IsFileReadable())
		{
			wxLogError(wxT("Can't open file %s"), memsave_fn.c_str());
			return;
		}

		unsigned long addr, len = fn.GetSize().ToULong();

		if (!len)
			return;

		wxString s;
		s.Printf(addrlen == 4 ? wxT("%04X") : wxT("%08X"), mv->GetAddr());
		selreg_addr->SetValue(s);
		selreg_len->Disable();
		selreg_lenlab->Disable();
		s.Printf(addrlen == 4 ? wxT("%04X") : wxT("%08X"), len);
		selreg_len->SetValue(s);
		selregion->SetWindowStyle(wxCAPTION | wxRESIZE_BORDER);

		if (gopts.keep_on_top)
			selregion->SetWindowStyle(selregion->GetWindowStyle() | wxSTAY_ON_TOP);
		else
			selregion->SetWindowStyle(selregion->GetWindowStyle() & ~wxSTAY_ON_TOP);

		if (selregion->ShowModal() != wxID_OK)
			return;

		selreg_addr->GetValue().ToULong(&addr, 16);
		MemLoad(memsave_fn, addr, len);
	}

	virtual void MemLoad(wxString &name, u32 addr, u32 len) = 0;

	void Save(wxCommandEvent &ev)
	{
		wxString s;
		s.Printf(addrlen == 4 ? wxT("%04X") : wxT("%08X"), mv->GetAddr());
		selreg_addr->SetValue(s);
		selreg_len->Enable();
		selreg_lenlab->Enable();
		selreg_len->SetValue(wxEmptyString);
		selregion->SetWindowStyle(wxCAPTION | wxRESIZE_BORDER);

		if (gopts.keep_on_top)
			selregion->SetWindowStyle(selregion->GetWindowStyle() | wxSTAY_ON_TOP);
		else
			selregion->SetWindowStyle(selregion->GetWindowStyle() & ~wxSTAY_ON_TOP);

		if (selregion->ShowModal() != wxID_OK)
			return;

		unsigned long addr, len;
		selreg_addr->GetValue().ToULong(&addr, 16);
		selreg_len->GetValue().ToULong(&len, 16);

		if (memsave_fn.empty())
			memsave_fn = wxGetApp().frame->GetPanel()->game_name() + wxT(".dmp");

		wxString pats = _("Memory dumps (*.dmp;*.bin)|*.dmp;*.bin|");
		pats.append(wxALL_FILES);
		wxFileDialog dlg(this, _("Select output file"), memsave_dir, memsave_fn,
		                 pats, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
		int ret = dlg.ShowModal();
		memsave_dir = dlg.GetDirectory();
		memsave_fn = dlg.GetPath();

		if (ret != wxID_OK)
			return;

		MemSave(memsave_fn, addr, len);
	}

	virtual void MemSave(wxString &name, u32 addr, u32 len) = 0;
protected:
	int addrlen;
	wxChoice* bs;
	wxTextCtrl* goto_addr;
	MemView* mv;

	wxDialog* selregion;
	wxTextCtrl* selreg_addr, *selreg_len;
	wxControl* selreg_lenlab;
	wxString memsave_fn;

	DECLARE_EVENT_TABLE()
};

BEGIN_EVENT_TABLE(MemViewerBase, Viewer)
	EVT_CHOICE(XRCID("BlockStart"), MemViewerBase::BlockStart)
	EVT_BUTTON(XRCID("Goto"), MemViewerBase::GotoEv)
	EVT_TEXT_ENTER(XRCID("GotoAddress"), MemViewerBase::GotoEv)
	EVT_BUTTON(XRCID("Refresh"), MemViewerBase::RefreshCmd)
	EVT_BUTTON(wxID_SAVE, MemViewerBase::Save)
	EVT_BUTTON(wxID_OPEN, MemViewerBase::Load)
END_EVENT_TABLE()

class MemViewer : public MemViewerBase
{
public:
	MemViewer() : MemViewerBase(~0)
	{
		bs->Append(_("0x00000000 - BIOS"));
		bs->Append(_("0x02000000 - WRAM"));
		bs->Append(_("0x03000000 - IRAM"));
		bs->Append(_("0x04000000 - I/O"));
		bs->Append(_("0x05000000 - PALETTE"));
		bs->Append(_("0x06000000 - VRAM"));
		bs->Append(_("0x07000000 - OAM"));
		bs->Append(_("0x08000000 - ROM"));
		bs->SetSelection(1);
		Fit();
	}

	// wx-2.8.4 or MacOSX compiler can't resolve overloads in evt table
	void RefillListEv(wxCommandEvent &ev)
	{
		Update();
	}

	void Update()
	{
		u32 addr = mv->topaddr;
		mv->words.resize(mv->nlines * 4);

		for (int i = 0; i < mv->nlines; i++)
		{
			if (i && !addr)
				break;

			for (int j = 0; j < 4; j++, addr += 4)
				mv->words[i * 4 + j] = CPUReadMemoryQuick(addr);
		}

		mv->Refill();
	}

	void WriteVal(wxCommandEvent &ev)
	{
		switch (mv->fmt)
		{
		case 0:
			CPUWriteByteQuick(mv->writeaddr, mv->writeval);
			break;

		case 1:
			CPUWriteHalfWordQuick(mv->writeaddr, mv->writeval);
			break;

		case 2:
			CPUWriteMemoryQuick(mv->writeaddr, mv->writeval);
			break;
		}
	}

	void MemLoad(wxString &name, u32 addr, u32 len)
	{
		wxFFile f(name, wxT("rb"));

		if (!f.IsOpened())
			return;

		// this does the equivalent of the CPUWriteMemoryQuick()
		while (len > 0)
		{
			memoryMap m = map[addr >> 24];
			u32 off = addr & m.mask;
			u32 wlen = (off + len) > m.mask ? m.mask + 1 - off : len;
			wlen = f.Read(m.address + off, wlen);

			if (wlen < 0)
				return; // FIXME: give error

			len -= wlen;
			addr += wlen;
		}
	}

	void MemSave(wxString &name, u32 addr, u32 len)
	{
		wxFFile f(name, wxT("wb"));

		if (!f.IsOpened())
			return;

		// this does the equivalent of the CPUReadMemoryQuick()
		while (len > 0)
		{
			memoryMap m = map[addr >> 24];
			u32 off = addr & m.mask;
			u32 wlen = (off + len) > m.mask ? m.mask + 1 - off : len;
			wlen = f.Write(m.address + off, wlen);

			if (wlen < 0)
				return; // FIXME: give error

			len -= wlen;
			addr += wlen;
		}
	}

	DECLARE_EVENT_TABLE()
};

BEGIN_EVENT_TABLE(MemViewer, MemViewerBase)
	EVT_COMMAND(wxID_ANY, EVT_REFILL_NEEDED, MemViewer::RefillListEv)
	EVT_COMMAND(wxID_ANY, EVT_WRITEVAL, MemViewer::WriteVal)
END_EVENT_TABLE()

class GBMemViewer : public MemViewerBase
{
public:
	GBMemViewer() : MemViewerBase((u16)~0)
	{
		bs->Append(_("0x0000 - ROM"));
		bs->Append(_("0x4000 - ROM"));
		bs->Append(_("0x8000 - VRAM"));
		bs->Append(_("0xA000 - SRAM"));
		bs->Append(_("0xC000 - RAM"));
		bs->Append(_("0xD000 - WRAM"));
		bs->Append(_("0xFF00 - I/O"));
		bs->Append(_("0xFF80 - RAM"));
		bs->SetSelection(1);
		Fit();
	}

	// wx-2.8.4 or MacOSX compiler can't resolve overloads in evt table
	void RefillListEv(wxCommandEvent &ev)
	{
		Update();
	}

	void Update()
	{
		u32 addr = mv->topaddr;
		mv->words.resize(mv->nlines * 4);

		for (int i = 0; i < mv->nlines; i++)
		{
			if (i && !(u16)addr)
				break;

			for (int j = 0; j < 4; j++, addr += 4)
				mv->words[i * 4 + j] = GBReadMemoryQuick(addr);
		}

		mv->Refill();
	}

	void WriteVal(wxCommandEvent &ev)
	{
		switch (mv->fmt)
		{
		case 0:
			GBWriteByteQuick(mv->writeaddr, mv->writeval);
			break;

		case 1:
			GBWriteHalfWordQuick(mv->writeaddr, mv->writeval);
			break;

		case 2:
			GBWriteMemoryQuick(mv->writeaddr, mv->writeval);
			break;
		}
	}

	void MemLoad(wxString &name, u32 addr, u32 len)
	{
		wxFFile f(name, wxT("rb"));

		if (!f.IsOpened())
			return;

		// this does the equivalent of the GBWriteMemoryQuick()
		while (len > 0)
		{
			u8* maddr = gbMemoryMap[addr >> 12];
			u32 off = addr & 0xfff;
			u32 wlen = (off + len) > 0xfff ? 0x1000 - off : len;
			wlen = f.Read(maddr + off, wlen);

			if (wlen < 0)
				return; // FIXME: give error

			len -= wlen;
			addr += wlen;
		}
	}

	void MemSave(wxString &name, u32 addr, u32 len)
	{
		wxFFile f(name, wxT("wb"));

		if (!f.IsOpened())
			return;

		// this does the equivalent of the GBReadMemoryQuick()
		while (len > 0)
		{
			u8* maddr = gbMemoryMap[addr >> 12];
			u32 off = addr & 0xfff;
			u32 wlen = (off + len) > 0xfff ? 0x1000 - off : len;
			wlen = f.Write(maddr + off, wlen);

			if (wlen < 0)
				return; // FIXME: give error

			len -= wlen;
			addr += wlen;
		}
	}

	DECLARE_EVENT_TABLE()
};

BEGIN_EVENT_TABLE(GBMemViewer, MemViewerBase)
	EVT_COMMAND(wxID_ANY, EVT_REFILL_NEEDED, GBMemViewer::RefillListEv)
	EVT_COMMAND(wxID_ANY, EVT_WRITEVAL, GBMemViewer::WriteVal)
END_EVENT_TABLE()
}

void MainFrame::MemViewer()
{
	switch (panel->game_type())
	{
	case IMAGE_GBA:
		LoadXRCViewer(Mem);
		break;

	case IMAGE_GB:
		LoadXRCViewer(GBMem);
		break;
	}
}
